package com

// ----------------------------------------------------------------------------
// 文件：model/security.go
// 描述：安全相关模块
// 版本：0.1
// 作者：lg
// 日期：2024-12-08
// 修改：2024-12-08 0.1 lg  新增版本
// ----------------------------------------------------------------------------

import (
	"crypto/md5"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"fmt"
	"math/big"
	"odm/config"
	"odm/logger"
	"os"
	"path"
	"time"

	"github.com/dgrijalva/jwt-go"
	"golang.org/x/crypto/bcrypt"
)

// GenToken gen token
func GenToken(user string, role string) (string, error) {
	conf := config.Defalut()
	token := jwt.New(jwt.SigningMethodHS256)
	claims := make(jwt.MapClaims)
	claims["exp"] = time.Now().Add(conf.GetDuration("server.expired") * time.Minute).Unix()
	claims["iat"] = time.Now().Unix()
	claims["user"] = user
	claims["role"] = role
	token.Claims = claims
	key := []byte(conf.GetString("server.key"))
	logger.Infof("key=%d", key)
	data, err := token.SignedString(key)
	if err != nil {
		logger.Errorf("获取令牌失败 [%s]", err)
		return "", errors.New("获取令牌失败")
	}
	logger.Infof("Gen token success:[%s]", data)
	return data, nil
}

// getSeckey get seckey
func getSeckey(token *jwt.Token) (interface{}, error) {
	conf := config.Defalut()
	key := []byte(conf.GetString("server.key"))
	logger.Infof("key=%d", key)
	return key, nil
}

// VerifyToken verify token
func VerifyToken(tokenString string) (string, error) {
	conf := config.Defalut()
	token, err := jwt.Parse(tokenString, getSeckey)
	if err != nil {
		if err.Error() == "Token is expired" {
			expired := conf.GetInt("server.expired")
			logger.Warnf("用户登录超过%d小时,请重新登录", expired)
			return "", fmt.Errorf("用户登录超过%d小时,请重新登录", expired)
		}
		logger.Warnf("令牌解析失败 [%s]", err)
		return "", errors.New("令牌解析失败")
	}
	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		logger.Warn("令牌校验失败")
		return "", errors.New("令牌校验失败")
	}
	if !token.Valid {
		logger.Warn("无效的令牌")
		return "", errors.New("无效的令牌")
	}
	username := claims["user"].(string)
	logger.Debugf("Verify user %s token success", username)
	return username, nil
}

// GetMd5String get md5 string,return length is 32
func GetMd5String(str string) string {
	data := []byte(str)
	has := md5.Sum(data)
	return fmt.Sprintf("%x", has)
}

// GetRandomString get a random string
func GetRandomString(length uint8, allowedChars ...[]rune) (string, error) {
	var letters = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
	if len(allowedChars) > 0 {
		letters = allowedChars[0]
	}
	letterLen := int64(len(letters))
	data := make([]rune, length)
	for index := range data {
		pos, err := rand.Int(rand.Reader, big.NewInt(letterLen))
		if err != nil {
			return "", err
		}
		data[index] = letters[pos.Int64()]
	}
	return string(data), nil
}

// GetHashString function get hash string
func GetHashString(str string) (string, error) {
	data, err := bcrypt.GenerateFromPassword([]byte(str), bcrypt.DefaultCost)
	return string(data), err
}

// GetPassword get a new password, return password, salt ,err
func GetPassword(password string, isMd5 bool) (string, string, error) {
	if !isMd5 {
		password = GetMd5String(password)
	}
	salt, err := GetRandomString(32)
	if err != nil {
		return "", "", err
	}
	hashPass, err := GetHashString(salt + password)
	return hashPass, salt, err
}

// GenRSAKey get rsa private key and public key.
//
// return { publicKey, error }
func GenRSAKey(bits int) error {
	//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
	//Reader是一个全局、共享的密码用强随机数生成器
	privateKey, err := rsa.GenerateKey(rand.Reader, bits)
	if err != nil {
		return err
	}
	//通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
	privateKeyX509 := x509.MarshalPKCS1PrivateKey(privateKey)
	//构建一个pem.Block结构体对象
	privateBlock := pem.Block{Type: "RSA Private Key", Bytes: privateKeyX509}
	//创建文件保存私钥
	privateFile, err := os.Create(path.Join("data", "private.pem"))
	if err != nil {
		return err
	}
	defer privateFile.Close()
	//将数据保存到文件
	if err := pem.Encode(privateFile, &privateBlock); err != nil {
		return err
	}
	// 获取公钥的数据
	publicKey := privateKey.PublicKey
	publicKeyX509, err := x509.MarshalPKIXPublicKey(&publicKey)
	if err != nil {
		return err
	}
	//创建一个pem.Block结构体对象
	publicBlock := pem.Block{Type: "RSA Public Key", Bytes: publicKeyX509}
	publicFile, err := os.Create(path.Join("data", "public.pem"))
	if err != nil {
		return err
	}
	defer publicFile.Close()
	if err := pem.Encode(publicFile, &publicBlock); err != nil {
		return err
	}
	return nil
}

// VerifyPassword function vertiy password
func VerifyPassword(hashPass string, password string) error {
	err := bcrypt.CompareHashAndPassword([]byte(hashPass), []byte(password))
	if err != nil {
		logger.Warn("用户或密码无效", "error", err)
		return errors.New("用户或密码无效")
	}
	logger.Debug("Verify password success")
	return nil
}

// RSADecrypt decrypt by RSA.
//
// return { plaintext, error }
func RSADecrypt(ciphertext string) (string, error) {
	code, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		logger.Errorf("密文解码失败：%s", err)
		return "", fmt.Errorf("密文解码失败：%s", err)
	}
	file, err := os.Open(path.Join("data", "private.pem"))
	if err != nil {
		logger.Errorf("打开私钥文件private.pem失败:%s", err)
		return "", fmt.Errorf("打开私钥文件private.pem失败:%s", err)
	}
	defer file.Close()
	//获取文件内容
	info, err := file.Stat()
	if err != nil {
		logger.Errorf("取私钥文件private.pem信息失败:%s", err)
		return "", fmt.Errorf("取私钥文件private.pem信息失败:%s", err)
	}
	buf := make([]byte, info.Size())
	file.Read(buf)
	//pem解码
	block, _ := pem.Decode(buf)
	if block == nil {
		logger.Error("pem解码失败:无效的密钥")
		return "", errors.New("pem解码失败:无效的密钥")
	}
	//X509解码
	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		logger.Errorf("X509解码失败:%s", err)
		return "", fmt.Errorf("X509解码失败:%s", err)
	}
	//对密文进行解密
	plainText, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, code)
	if err != nil {
		logger.Errorf("密文解密失败：%s", err)
		return "", fmt.Errorf("密文解密失败：%s", err)
	}
	//返回明文
	return string(plainText), nil
}
